/*
 * Zombinoes: Peter Mosher, Michael Pitts
 * TCSS 491 - Computational Worlds
 * Project 1
 */

import graphics.InternalNode;
import graphics.Node;
import graphics.Shape;
import graphics.Tree;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.List;

import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLCanvas;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

import physics.PhysicalCollection;
import physics.PhysicsLib;

/**
 * This is the main class, which starts the graphics display.
 * @author Michael Pitts, Peter Mosher
 * @version Jan 23, 2012
 */
public class Main {
	
	/** The physics tree. */
	private static final PhysicalCollection physics = new PhysicalCollection(PhysicsLib.getObjects());
	
	/** The graphics tree that will display. */
	private static final Tree tree = new Tree(physics);
	private static final GLProfile glp = GLProfile.getDefault();
 	private static final GLCapabilities caps = new GLCapabilities(glp);
	
	/** The graphics canvas. */
 	private static GLCanvas canvas = new GLCanvas(caps);
 	
 	private static Shape my_current_shape;
	private static boolean invert_cont = false;
	/**
	 * Starts everything.
	 * @param args command line arguments.
	 */
	public static void main(final String[] args) {
		canvas.setFocusable(true);
		canvas.addGLEventListener(tree);
		final JFrame frame = getFrame();
		frame.add(canvas);

		
		//canvas.addKeyListener(getKeyListener());
		//canvas.addMouseListener(getMouseListener());
		//canvas.addMouseMotionListener(getMotion());
		
		frame.setVisible(true);
	}
	
	/**
	 * Gets the JFrame that will be the main dispaly for the graphics.
	 */
	private static JFrame getFrame() {
		final JFrame frame = new JFrame("Zombinoes!");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setMinimumSize(new Dimension(300, 300));
		frame.setFocusable(true);
		frame.setFocusableWindowState(true);
		frame.setEnabled(true);
		if (Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH)) {
			frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
		}
		frame.addKeyListener(getKeyListener());
		frame.setJMenuBar(getJMenuBar());
		return frame;
	}

	private static JMenuBar getJMenuBar() {
		final JMenuBar menubar = new JMenuBar();
		final JMenu file = new JMenu("File");
		JMenuItem item;
		
		item = new JMenuItem("add Circle");

        item.addActionListener(new ActionListener() {
            public void actionPerformed(final ActionEvent e) {
            	my_current_shape = Shape.CIRCLE;
            }
        });  
		//file.add(item);

		item = new JMenuItem("add Triforce");
        item.addActionListener(new ActionListener() {
            public void actionPerformed(final ActionEvent e) {
            	my_current_shape = Shape.TRI_FORCE;
            }
        });
		//file.add(item);
		
		item = new JMenuItem("add Square");
        item.addActionListener(new ActionListener() {
            public void actionPerformed(final ActionEvent e) {
            	my_current_shape = Shape.SQUARE;
            }
        });
		//file.add(item);
		
		item = new JMenuItem("add Hexagon");
        item.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            	my_current_shape = Shape.HEXAGON;
            }
        });
		//file.add(item);
		
		item = new JMenuItem("add Triangle");
        item.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            	my_current_shape = Shape.TRIANGLE;
            }
        });
		//file.add(item);
		
		file.addSeparator();
		item = new JMenuItem("Remove Tool");
        item.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            	my_current_shape = Shape.REMOVE;
            }
        });
		//file.add(item);
		file.addSeparator();
		
		item = new JMenuItem("Drag Tool");
        item.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            	my_current_shape = null;
            }
        });
		//file.add(item);
		file.addSeparator();
		file.add(getExit());
		menubar.add(file);
		return menubar;
	}

	private static JMenuItem getExit() {
		final JMenuItem exit = new JMenuItem("Exit");
		exit.addActionListener(new ActionListener(){
			@Override public void actionPerformed(final ActionEvent e) {
				System.exit(0);
			}
		});
		return exit;
	}

	/**
	 * 
	 * Adapter for motion of the Mouse.
	 * Where ever I click and drag, that becomes the new center point, the new origin
	 * One issue: the mouseDragged assumes that our window is square
	 * 
	 * **/
	private static MouseMotionAdapter getMotion() {
		return new MouseMotionAdapter(){
			@Override public void mouseDragged(final MouseEvent the_event) {
				if (my_current_shape == null) {
					dragEvent(the_event);
				}
			}
		};
	}
	
	/** Tracks the old mouse position to update node movement. */
	private static int my_old_mouse_x, my_old_mouse_y;
	
	/**
	 * Moves a selected node, depending on how the mouse was dragged.
	 * @param the_event is the drag event.
	 */
	private static void dragEvent(final MouseEvent the_event) {
		if (tree.getRoot().isSelected()) {
			final float scale_x, scale_y;
			if (canvas.getWidth() > canvas.getHeight()) {
				scale_x = 2 * (float) canvas.getWidth() / canvas.getHeight();
				scale_y = 2f;
			} else {
				scale_y = 2 * (float) canvas.getHeight() / canvas.getWidth();
				scale_x = 2f;
			}
			final Node node = tree.getSelectedBranch().get(1);
			final float delta_x = (float) (the_event.getX() - my_old_mouse_x ) /
					canvas.getWidth();
			final float delta_y = (float) (my_old_mouse_y - the_event.getY()) / 
					canvas.getHeight();
			my_old_mouse_x = the_event.getX();
			my_old_mouse_y = the_event.getY();
			node.setTranslate((node.getTranslateX()) + 1/tree.getRoot().getScaleX()*scale_x * delta_x, 
					(node.getTranslateY()) + 1/tree.getRoot().getScaleY()*scale_y * delta_y, node.getTranslateZ());
		}
	}
	
	/**
	 * Designed for Mouse Listener originally. 
	 * It could be combined with the Mouse Motion Listener
	 **/
	private static MouseAdapter getMouseListener() {
		return new MouseAdapter() {		
			@Override public void mousePressed(final MouseEvent the_event) {
				if (my_current_shape == null) {
					tree.setSelectMode(the_event.getX(), the_event.getY());
					my_old_mouse_x = the_event.getX();
					my_old_mouse_y = the_event.getY();
				} else if (my_current_shape != Shape.REMOVE) {
					addShape(my_current_shape.getShape(), 
							the_event.getX(), the_event.getY());
				} else { // remove selected shape
					tree.getRoot().update(InternalNode.REMOVE_NEXT);
					tree.setSelectMode(the_event.getX(), the_event.getY());
				}
			}
			
			@Override public void mouseReleased(final MouseEvent the_event) {
				if (tree.getRoot().isSelected()) {
					final List<Node> list = tree.getSelectedBranch();
					list.get(1).update();
				}
				tree.getRoot().clearSelect();
			}
		};
	}

	private static final float TRANS_DIF = 0.1f;
	private static final int ROTATE_DIF = 1;

	private static KeyListener getKeyListener() {
		return new KeyAdapter() {
			@Override public void keyPressed(final KeyEvent event) {
				int control =1;
				float factor = 0.1f;
				if(invert_cont) {control=-1;}
				
				switch (event.getKeyCode()) {
					case KeyEvent.VK_9: //invert controls
						if(invert_cont) {invert_cont=false;}
						else {invert_cont=true;}
						break;
					case KeyEvent.VK_UP:
						tree.getRoot().setTranslate(tree.getRoot().getTranslateX(), 
								tree.getRoot().getTranslateY() + TRANS_DIF*control, 
								tree.getRoot().getTranslateZ());
						break;
					case KeyEvent.VK_DOWN:
						tree.getRoot().setTranslate(tree.getRoot().getTranslateX(), 
								tree.getRoot().getTranslateY() - TRANS_DIF*control, 
								tree.getRoot().getTranslateZ());
						break;
					case KeyEvent.VK_LEFT:
						tree.getRoot().setTranslate(tree.getRoot().getTranslateX() - TRANS_DIF*control, 
								tree.getRoot().getTranslateY(), 
								tree.getRoot().getTranslateZ());
						break;
					case KeyEvent.VK_RIGHT:
						tree.getRoot().setTranslate(tree.getRoot().getTranslateX() + TRANS_DIF*control, 
								tree.getRoot().getTranslateY(), 
								tree.getRoot().getTranslateZ());
						break;
					case KeyEvent.VK_A:
						if (tree.getRoot().getRotateZ() == 0) {
							tree.getRoot().setRotate(tree.getRoot().getRotateAngle(), 
									tree.getRoot().getRotateX(), 
									tree.getRoot().getRotateY(), 1);
						}
						tree.getRoot().setRotate(tree.getRoot().getRotateAngle() + ROTATE_DIF, 
								tree.getRoot().getRotateX(), 
								tree.getRoot().getRotateY(), 
								tree.getRoot().getRotateZ());
						break;
					case KeyEvent.VK_D:
						if (tree.getRoot().getRotateZ() == 0) {
							tree.getRoot().setRotate(tree.getRoot().getRotateAngle(), 
									tree.getRoot().getRotateX(), 
									tree.getRoot().getRotateY(), 1);
						}
						tree.getRoot().setRotate(tree.getRoot().getRotateAngle() - ROTATE_DIF, 
								tree.getRoot().getRotateX(), 
								tree.getRoot().getRotateY(), 
								tree.getRoot().getRotateZ());
						break;
					case KeyEvent.VK_W:
						if(tree.getRoot().getScaleX() <= 0 || tree.getRoot().getScaleY() <= 0)
						{
							tree.getRoot().setScale(1f, 1f, 1);
						}
						// Zoom in
						//("Zoom in");
						
						
						tree.getRoot().setScale(tree.getRoot().getScaleX()*(1+factor), tree.getRoot().getScaleY()*(1+factor), 1);
						//System.out.println(tree.getRoot().getScaleX());
						break;
					case KeyEvent.VK_S:
						// Zoom out
						//("Zoom out");
						
						if(tree.getRoot().getScaleX() <= 0 || tree.getRoot().getScaleY() <= 0)
						{
							tree.getRoot().setScale(1f, 1f, 1);
						}
						tree.getRoot().setScale(tree.getRoot().getScaleX()*.90f, tree.getRoot().getScaleY()*.90f, 1);
						//System.out.println(tree.getRoot().getScaleX());
						break;
				}
			}
		};
	}

	/**
	 * Adds a new shape to the tree.
	 * @param the_node is the new shape.
	 * @param the_x is the new x position.
	 * @param the_y is the new y position.
	 */
	private static void addShape(final Node the_node, 
			final int the_x, final int the_y) {
		final float scale_x, scale_y;
		if (canvas.getWidth() > canvas.getHeight()) {
			scale_x = (float) canvas.getWidth() / canvas.getHeight();
			scale_y = 1f;
		} else {
			scale_y = (float) canvas.getHeight() / canvas.getWidth();
			scale_x = 1f;
		}
		final float x = scale_x * ( 2f * the_x / canvas.getWidth() - 1  )-tree.getRoot().getTranslateX();
		final float y = scale_y * ( 1 - 2f * the_y / canvas.getHeight()  )-tree.getRoot().getTranslateY();
		the_node.setTranslate(1/tree.getRoot().getScaleX()*x, 1/tree.getRoot().getScaleY()*y, 0);
		tree.getRoot().addChild(the_node);
	}
}
